package com.cskaoyan.javase.object._3equals;

/**
 * 方法的声明:
 * equals方法的声明，比较简单：
 * public boolean equals(Object obj)
 * 仅需要注意两点：
 *      1.该方法是有参数的，需要传入一个对象（任意一个对象就行）
 *      2.方法是有返回值的，返回一个布尔类型的值，真或假。
 *
 * 方法的作用
 * equals有相同，相等的意思。
 * 所以该方法的作用是，用于指示其他某个对象是否与此对象"相等"。
 * 此对象即调用该equals方法的对象，其它对象即方法实参传入的对象。
 *
 * 既然是比较对象相等，那自然就需要比较的规则才有意义
 * 比较的规则其实就是方法体中的代码
 * 我们先来看一下Object类当中，默认的比较规则，即equals方法在Object类当中的默认实现。
 *
 * equals默认实现
 * Object类当中的equals方法默认实现，认为的对象相等的比较规则：
 * public boolean equals(Object obj) {
 *  return (this == obj);
 * }
 * 很显然，这个比较规则非常简单。双等号直接连接引用，比较对象的地址
 * 换句话说是比较两个引用是否指向同一个对象。
 * 这个相等的规则就非常苛刻了：
 *
 * 只有当两个引用完全指向同一个对象时，方法才会返回true表示对象相等
 * 否则都会返回false表示对象不相等,只有自己和自己才相等.
 *
 *
 * 我们认定的对象相等是什么呢?
 *      1.如果不是相同类型的对象，没有可比性，直接认定为不相等。
 *      2.如果是相同类型的对象，行为是一致的，只有成员变量的取值有差异。
 *          所以只要，这两个对象的成员变量的取值（对象的状态）是一致时，就认定这两个对象相等。
 *
 * 如果需要自定义比较对象相等的规则,就需要在子类中重写equals方法
 * 重写equals方法的常规协定(文档中的规定):
 *      1.自反性：对于任何非空引用值 x，x.equals(x)都应返回 true
 *          自己和自己比较,仍然返回true
 *
 *      2.排他性：当比对的不是同种类型的对象或者是一个null时，默认返回false
 *          排除不同类型的对象,以及null元素
 *
 *      3.对称性：对于任何非空引用值 x 和 y
 *      当且仅当y.equals(x) 返回 true 时，x.equals(y) 才应返回true
 *          x = y 反过来 y = x
 *
 *      4.传递性：对于任何非空引用值 x、y 和 z
 *          如果x.equals(y)返回 true，并且 y.equals(z) 返回 true，那么x.equals(z) 应返回 true。
 *          x = y,y = z --> x = z
 *
 *      5.一致性：对于任何非空引用值 x 和 y
 *          多次调用 x.equals(y)始终返回 true 或始终返回 false。
 *          前提是,用于比较的条件没有发生变化
 *
 *      以上三点,只需要使用属性的取值,判断对象相等,就能够自然满足.
 *
 *
 * 提示/注意:
 *      1.日常开发中建议alt + insert自动重写equals方法
 *          除非特别有必要,不然不建议手写
 *
 *      2.浮点数比较数值大小
 *          用"=="在绝大多数情况下,是完全没有问题的
 *          但是浮点数比较特殊,它不仅仅有数值
 *          还包括一些非常特殊的非数值:
 *              比如无穷大,无穷小,非数(NaN)
 *          所以出于代码严谨的考虑
 *          建议比较浮点数的大小,不要用"=="
 *          而是用"包装类型.compare"方法
 *          具体来说就是:
 *              Float.compare(float v1,float v2)
 *              Double.compare(double v1,double v2)
 *           然后这两个方法都会返回一个int数值
 *           怎么看方法的返回值,决定谁大谁小呢?
 *           把这个方法看成v1 - v2
 *           如果方法返回小于0的数,说明v2大
 *           如果方法返回大于0的数,说明v1大
 *           如果方法返回值就是0,说明v1和v2相等
 *
 *        3.如果类中有引用数据类型成员变量
 *          需要去调用它们的equals方法完成比较。这就意味着还需要重写这个类的equals方法。
 *
 *        4.财务金额上的运算是不推荐使用浮点数的，会出现精度问题。
 *           推荐使用BigDecimal这个类完成运算。
 *
 * @since 14:27
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s = new Student(10, 100);
        Student s2 = new Student(10, 100);
        System.out.println((s == s2));
        System.out.println((s.equals(s2)));
        Student s3 = new Student(18, 100);
        System.out.println(s2.equals(s3));
    }
}

class Student {
    int age;
    double score;
    String name;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }*/

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }*/

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

// 按照常规协定重写equals方法
    /*@Override
    public boolean equals(Object obj) {
        // 1.满足自反性
        if (this == obj) {
            return true;
        }

        // 2.满足排它性
        if (obj == null || getClass() != obj.getClass()) {
            // 这说明obj是null,或者指向的对象不是Student类型对象
            return false;
        }

        // 3.用属性的取值判断对象相等,以满足对称性,传递性,一致性
        // 代码运行到这里,obj指向的对象一定是Student类型,可以直接做强转
        Student target = (Student) obj;

        // 挨个比较属性取值
        if (age != target.age) {
            return false;
        }
        return score == target.score;
    }*/

}